ASP.NET Core 2.0入门

Razor增删改简单示例

作者:陈广 日期:2018-3-11


上篇文章,我们简单了解了下Razor,大概知道了Razor是怎么回事。今天来做一个稍微复杂点的程序。这个例子源自ASP.NET Core文档中的一篇文章:ASP.NET Core中的Razor页面介绍。当然,我的风格是极简,把这个例子能简化的地方全部简化了。越简单,越容易入门,牵涉的东西太多,脑袋容易搞不清状况。学完我这篇文章,再去看微软的文章,会更容易理解一些。接下来跟我一起做例子。

新建项目

  1. 新建文件夹RazorDemo,在文件夹上点鼠标右键,选择Open with Code,使用Visual Studio Code打开这个文件夹。
  2. Ctrl+Shift+m打开终端,输入如下命令:
dotnet new empty
  1. 更改新生成的Startup.cs文件中的Startup类代码如下:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

这些步骤和上一篇文章完全一样。

加入数据层

在之前写的HTTP协议使用这篇文章中,为了方便,我们把数据直接加到了Controller里面,当时访问数据时没有做任何页面。而这次的程序,不但有页面,还会有多个页面访问数据,所以必须要把数据独立出来。当然,现在还没有涉及到数据库,所以还是使用一个集合来代替数据库,程序关闭,数据消失。另外还需要针对增删改写专门的方法以方便页面调用。

在项目根目录下新建一个Data文件夹,并在Data文件夹下新建一个Student.cs文件。输入如下代码:

using System.Collections.Generic;

namespace RazorPage.Data
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        //无参构造函数必须写,否则Create页面无法使用 [BindProperty]
        public Student() { }
    }
    public static class Students
    {
        public static List<Student> Stus { get; set; } = new List<Student>();
        //添加一个学生
        public static void Add(Student stu)
        {
            Stus.Add(stu);
        }
        //通过id获取相应的Student对象
        public static Student GetById(int id)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    return Stus[i];
                }
            }
            return null;
        }
        //删除一个学生,删除成功返回元素位置索引,失败返回-1
        public static int Delete(int id)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    Stus.RemoveAt(i);
                    return i;
                }
            }
            return -1;
        }
        //更改指定Id的学生的姓名,成功返回修改元素位置索引,失败返回-1
        public static int Update(int id,string name)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    Stus[i].Name = name;
                    return i;
                }
            }
            return -1;
        }
    }
}

此页面创建了一个Students类,用于存放多个Student的信息,并添加了增删改查方法,这些之后都要使用到。当然,为简单,代码并不健壮,比如未考虑Id相同的可能性。

主页面

主页面Index的功能有:浏览所有Student、到新建Student页面的链接、到编辑Student页面的链接、删除Student。

在项目根目录下新建一个Pages文件夹,然后在Pages文件夹下面新建一个Index.cshtml文件,再建一个Code-behind文件Index.cshtml.cs

浏览所有Student

Index.cshtml文件中输入如下代码:

@page
@model RazorPage.Pages.IndexModel
<h1>学生列表</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>学号</th>
                <th>姓名</th>
            </tr>
        </thead>
        <tbody>
            @foreach(var stu in Model.Stus)
            {
                <tr>
                    <td>@stu.Id</td>
                    <td>@stu.Name</td>
                </tr>
            }
        </tbody>
    </table>
</form>

在code-behind文件Index.cshtml.cs中输入如下代码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using RazorPage.Data;
namespace RazorPage.Pages
{
    public class IndexModel : PageModel
    {
        public IList<Student> Stus { get; private set; }
        public void OnGet()
        {
            //这两句添加数据的代码仅用于浏览测试
            Students.Stus.Add(new Student() { Id = 1, Name = "张三" });
            Students.Stus.Add(new Student() { Id = 2, Name = "李四" });
            Stus = Students.Stus;
        }
    }
}

code-behind文件中的OnGet()方法对就的就是HTTP中的Get请求,请参考HTTP协议使用,先看完它再来看本文。也就是说当浏览器发送Get请求时,会触发code-behind文件中的OnGet()方法。OnGet()方法相应的异步方法为OnGetAsync(),这里由于网页是自己一个人学习使用,为了简单,只使用了同步方法,在实际开发中面对的是多用户操作,应尽量使用异步方法。

OnGet()方法方法中我们将Stus属性指向之前创建的数据类。而Stus也是code-behind文件和页面文件间的数据传输通道,也就是说页面文件通过code-behind文件的Stus属性来获取数据。

Stus指向Students之前,我们往Students里放了两条数据,这只是为了页面有数据可显示,等下运行完程序这两句就可以删掉了。

运行程序,效果如下图所示: Index.cshtml文件中我们使用的是@foreach指令来创建表格显示数据,非常方便。我们刷新页面,会发现张三、李四会不停地重复添加,这当然是不对的。所以接来下专门做一个页面用于添加数据。

添加新项页面

创建Create页面

Pages文件夹下面新建一个Create.cshtml文件,再建一个Code-behind文件Create.cshtml.cs

Create.cshtml文件中输入如下代码:

@page
@model RazorPage.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
    <body>
        <p>请输入新的学生信息</p>
        <form method="POST">
            <div>学号:<input asp-for="Stu.Id"/></div>
            <div>姓名:<input asp-for="Stu.Name"/></div>
            <input type="submit"/>
        </form>
    </body>
</html>

这里我们使用了表单,让用户输入数据,并在表单中使用标签助手asp-for绑定code-behind文件的Stu属性中的IdName属性。有关标签助手,微软写了非常详细的文档,我已翻译完毕,请参阅相关章节。

Create.cshtml.cs文件中输入如下代码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPage.Data;

namespace RazorPage.Pages
{
    public class CreateModel : PageModel
    {
        [BindProperty]
        public Student Stu { get; set; }
        public IActionResult OnPost()
        {
            Students.Add(Stu);
            return RedirectToPage("/Index");
        }
    }
}

这里声明了Stu属性,它是code-behind文件一页面的交流通道,需要注意的是它使用了[BindProperty]特性。[BindProperty]使得一个属性可以实现双向绑定。这里我们要有一个概念,页面是在客户端(浏览器)上运行的,而code-behind文件是在服务器上运行的。从上篇文章中我们知道,在Razor页面中可以通过@来访问code-behind文件中的属性值。在使用asp-for标签助手将一个Input绑定到这个属性时,我们还希望Input中值的改变可以影响到服务器中的code-behind文件里的属性值,这时就需要给属性加上[BindProperty]特性。当然,这种改变需要在表单提交之后才会传导到服务器。

当浏览器通过表单向服务器提交Post请求时,会触发OnPost()方法,在这个方法里,表单的Post请求会更新Stu的属性值,然后我们将更新后的对象加入到Students内,从而实现了数据的添加。

最后,通过返回RedirectToPage("/Index"),将页面重定向至Index页面,当然Index页面会重新获取服务器端的数据,从而将新加的数据显示出来。接下来需要在Index页面中添加一个指向Create页面的链接。

更改Index页面

更改Index.cshtml文件如下:

@page
@model RazorPage.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>学生列表</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>学号</th>
                <th>姓名</th>
            </tr>
        </thead>
        <tbody>
            @foreach(var stu in Model.Stus)
            {
                <tr>
                    <td>@stu.Id</td>
                    <td>@stu.Name</td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="./Create">新建</a>
</form>

这里我们在窗体下方创建了一个<a>标签,并使用标签助手asp-page<a>标签的链接指向了Create页面。

运行程序,效果如下。

添加删除功能

实现添加条目的功能后,有了数据,我们现在可以实现删除功能了。删除功能直接在Index页面上实现。

Index.cshtml文件中,更改<tbody>标签部分代码如下:

<tbody>
    @foreach(var stu in Model.Stus)
    {
        <tr>
            <td>@stu.Id</td>
            <td>@stu.Name</td>
            <td>
                <button type="submit" asp-page-handler="delete"
                        asp-route-id="@stu.Id">删除</button>
            </td>
        </tr>
    }
</tbody>

我们在每个条目的后面增加了一个删除按钮,并使用asp-page-handler标签助手指明此按钮动作为删除动作,使用asp-route-id标签助手来传递删除条目的Id值。

打开Index.cshtml.cs文件,在IndexModel类中添加如下方法:

public IActionResult OnPostDelete(int id)
{
    Students.Delete(id);
    return RedirectToPage();
}

注意,我们这里删除数据并没有使用HTTP的Delete方法,而是使用Post方法进行删除,这是因为我们使用的是表单,它只有Post和Get。为表明此Post执行的是删除操作,在页面中使用了asp-page-handler="delete"进行指明。使得在按下删除按钮之后,会触发code-behind文件中的OnPostDelete方法。在此方法中传递了一个参数id,通过此id,我们可以删除相应条目。然后重定向回本页面。

运行程序,添加几个条目后,效果如下图所示:

添加编辑功能

创建Edit页面

Pages文件夹下面新建一个Edit.cshtml文件,再建一个Code-behind文件Edit.cshtml.cs

Edit.cshtml文件中输入如下代码:

@page "{id:int}"
@model RazorPage.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>编辑学生-@Model.Stu.Id</h1>
<form method="POST">
    <input asp-for="Stu.Id" type="hidden"/>
    <div>
        <input asp-for="Stu.Name"/>
    </div>
    <div>
        <button type="submit">保存</button>
    </div>
</form>

虽然本例的id不允许更改,但还是为Stu.id设置了一个<input>。这是因为Code-behind文件的Stu属性设置了[BindProperty],它的两个字段都必须和<input>连接,即使id不允许更改,折中的办法就是把id所对应的<input>隐藏起来,这样就更改不了了。

Edit.cshtml.cs文件中输入如下代码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPage.Data;

namespace RazorPage.Pages
{
    public class EditModel : PageModel
    {
        [BindProperty]
        public Student Stu { get; set; }
        public IActionResult OnGet(int id)
        {
            Student stu1 = Students.GetById(id);
            if (stu1 == null)
            {
                return RedirectToPage("/Index");
            }
            Stu = new Student() { Id = stu1.Id, Name = stu1.Name };
            return Page();
        }

        public IActionResult OnPost()
        {   //更新数据
            Students.Update(Stu.Id, Stu.Name);
            return RedirectToPage("/Index");
        }
    }
}

在页面发送Get请求时,我们从Student克隆了相应id的Student并赋予Stu属性。在页面发送Post请求更改数据时,再把Stu内更新的数据重新返还给Student。这里没有办法直接对Strudents相应的Student直接更新。

Index页面增加编辑链接

接下来为每个条目添加一个编辑链接,打开Index.cshtml文件,更改<tbody>代码如下:

<tbody>
    @foreach(var stu in Model.Stus)
    {
        <tr>
            <td>@stu.Id</td>
            <td>@stu.Name</td>
            <td>
                <a asp-page="./Edit" asp-route-id="@stu.Id">编辑</a>
                <button type="submit" asp-page-handler="delete"
                        asp-route-id="@stu.Id">删除</button>
            </td>
        </tr>
    }
</tbody>

运行程序,添加几个条目后进行更改,效果如下:

数据持久化

上面写的程序,重新启动后之前数据都会消失,这让人多少有些不爽。但现在还不想涉及数据库,有什么办法可以永久保存数据呢?当然有,最方便的莫过于json了。现如今json大行其道,大部分的配置文件都使用json进行保存。试问json编程哪家强,当属json.net,速度最快,使用最方便。更让人高兴的是.NET Core好象已经集成了json.net。只需引入Newtonsoft.Json命名空间就可以使用了。

当然程序创建时已经分好层,我们只需更改Student.cs文件即可,其它地方都不需要动。将Student.cs文件代码更改如下:

using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace RazorPage.Data
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        //无参构造函数必须写,否则Create页面无法使用 [BindProperty]
        public Student() { }
    }
    public static class Students
    {
        public static List<Student> Stus { get; set; } = new List<Student>();
        //静态构造函数
        static Students()
        {   //如果存在json文件,则读取并返序列化为List<Student>对象
            if (File.Exists("Students.json"))
            {
                string s = File.ReadAllText("Students.json");
                Stus = JsonConvert.DeserializeObject<List<Student>>(s);
            }
        }
        //添加一个学生
        public static void Add(Student stu)
        {
            Stus.Add(stu);
            //将Stus序列化为json文件,以进行持久保存
            string s = JsonConvert.SerializeObject(Stus);
            File.WriteAllText("Students.json", s);
        }
        public static Student GetById(int id)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    return Stus[i];
                }
            }
            return null;
        }
        //删除一个学生,删除成功返回元素位置索引,失败返回-1
        public static int Delete(int id)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    Stus.RemoveAt(i);
                    string s = JsonConvert.SerializeObject(Stus);
                    File.WriteAllText("Students.json", s);
                    return i;
                }
            }
            return -1;
        }
        //更改指定Id的学生的姓名,成功返回修改元素位置索引,失败返回-1
        public static int Update(int id, string name)
        {
            for (int i = 0; i < Stus.Count; i++)
            {
                if (Stus[i].Id == id)
                {
                    Stus[i].Name = name;
                    string s = JsonConvert.SerializeObject(Stus);
                    File.WriteAllText("Students.json", s);
                    return i;
                }
            }
            return -1;
        }
    }
}

运行程序,然后执行各种操作,关闭程序,再打开,之前的数据已经保存下来。

好,程序写完,现在可以去看创建一个web应用程序这篇文章了。

;

© 2018 - IOT小分队文章发布系统 v0.3